package com.xindao.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.FontMetrics;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.MotionEvent;
import android.widget.TextView;

public class LianLianTextView extends TextView {
  private String[] poemText;
  private Rect drawArea, fontSize, lineArea;
  private int itemWidth, itemHeight;
  private Line[] lines = new Line[4];
  private int lastLinePosition;
  private SparseArray<PointF> coordinate;
  private OnLineConnectedListener lineConnectListener;
  private PerformConnect performConnect;

  public LianLianTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    creatRect();
  }

  public LianLianTextView(Context context) {
    this(context, null);
  }

  private void creatRect() {
    drawArea = new Rect();
    fontSize = new Rect();
    lineArea = new Rect();
    for (int i = 0; i < lines.length; i++) {
      lines[i] = new Line();
    }
    coordinate = new SparseArray<PointF>(8);
    for (int i = 0; i < 8; i++) {
      coordinate.put(i, new PointF());
    }
  }

  public void setPoemText(String[] text) {
    poemText = text;
    requestLayout();
  }

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    drawArea.left = getPaddingLeft();
    drawArea.top = getPaddingTop();
    drawArea.right = getWidth() - getPaddingRight();
    drawArea.bottom = getHeight() - getPaddingBottom();
    computeFontSize();
    itemWidth = drawArea.width() / 4;
    if (null != poemText && poemText.length > 0) {
      FontMetrics met = getPaint().getFontMetrics();
      itemHeight = (int) (poemText[0].length() * (met.descent - met.ascent));
      lineArea.top = itemHeight + getPaddingTop();
      lineArea.right = getWidth();
      lineArea.bottom = getHeight() - itemHeight - getPaddingBottom();
      float x, y;
      for (int i = 0; i < 8; i++) {
        x = drawArea.left + itemWidth * (i % 4) + itemWidth / 2;
        y = i < 4 ? drawArea.top + itemHeight : drawArea.bottom - itemHeight;
        coordinate.get(i).set(x, y);
      }
    }
  }

  private void computeFontSize() {
    getPaint().getTextBounds("数", 0, 1, fontSize);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (null == poemText || poemText.length == 0) return;
    int aW = drawArea.width() / 4;
    float drawX = (aW - fontSize.width()) / 2;
    FontMetrics font = getPaint().getFontMetrics();
    float drawY = drawArea.top - getPaint().getFontMetrics().top;
    for (int i = 0; i < poemText.length; i++) {
      if (i < 4) {
        drawY = drawArea.top - font.ascent;
      } else {
        drawY = drawArea.bottom - poemText[i].length() * (font.descent - font.ascent) - font.ascent;
      }
      drawX = aW * (i % 4) + (aW - fontSize.width()) / 2 + drawArea.left;
      drawTextVertical(canvas, drawX, drawY, poemText[i]);
    }
    for (int i = 0; i < lines.length; i++) {
      if (!lines[i].isEmpty()) {
        canvas.drawPath(lines[i].path, getPaint());
      }
    }
  }

  private void drawTextVertical(Canvas canvas, float x, float y, String text) {
    Paint paint = getPaint();
    FontMetrics metric = paint.getFontMetrics();
    float[] points = new float[text.length() * 2];
    for (int i = 0; i < text.length(); i++) {
      points[i * 2] = x;
      points[i * 2 + 1] = y;
      canvas.drawText(String.valueOf(text.charAt(i)), x, y, paint);
      y += metric.descent - metric.ascent;
    }
    // canvas.drawPosText(text, points, paint);
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if (null == poemText || poemText.length == 0) {
      return super.onTouchEvent(event);
    } else {
      int action = event.getAction();
      boolean value = true;
      float x = event.getX(), y = event.getY();
      int point = getTouchPoint(x, y);
      switch (action) {
      case MotionEvent.ACTION_DOWN:
        if (lineArea.contains((int) x, (int) y)) {
          value = false;
          return false;
        }

        lastLinePosition = getTouchedLine(point);
        lines[lastLinePosition].start(point);
        break;
      case MotionEvent.ACTION_MOVE:
        if (lastLinePosition != -1) {
          lines[lastLinePosition].lineTo(x, y);
        }
        break;
      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_CANCEL:
        if (lastLinePosition != -1) {
          lines[lastLinePosition].lineEnd(point);
          for (int i = 0; i < lines.length; i++) {
            if (i != lastLinePosition && !lines[i].isEmpty() && lines[i].isSameStartOrEnd(lines[lastLinePosition])) {
              lines[lastLinePosition].empty();
              break;
            }
          }
          if (lastLinePosition != -1) {
            if (null == performConnect) performConnect = new PerformConnect();
            performConnect.setLine(lines[lastLinePosition]);
            post(performConnect);
          }
        }
        lastLinePosition = -1;
        break;
      default:
        break;
      }
      invalidate();
      return value;
    }
  }

  private int getTouchPoint(float x, float y) {
    x = Math.max(drawArea.left, Math.min(x, drawArea.right));
    int time = y < lineArea.centerY() ? 0 : 1;
    return time * 4 + Math.min(3, (int) ((x - drawArea.left) / itemWidth));
  }

  private int getTouchedLine(int point) {
    for (int i = 0; i < lines.length; i++) {
      if (lines[i].contains(point)) return i;
    }
    for (int i = 0; i < lines.length; i++) {
      if (lines[i].isEmpty()) return i;
    }
    return 0;
  }

  public void setOnLineConnectedListener(OnLineConnectedListener listener) {
    lineConnectListener = listener;
  }

  protected void performLineConnected(Line line) {
    if (null != lineConnectListener) {
      lineConnectListener.onLineConnected(line);
    }

  }

  public class Line {
    private float startX, startY;
    private Path path;
    private int start, end;

    private Line() {
      path = new Path();
      start = end = -1;
    }

    private void start(int point) {
      PointF p = coordinate.get(point);
      start = point;
      end = -1;
      startX = p.x;
      startY = p.y;
    }

    private void lineTo(float x, float y) {
      float dx = x - startX, dy = y - startY;
      float dz = (float) Math.sqrt(dx * dx + dy * dy);
      path.reset();
      path.moveTo(startX - 3, startY);
      path.lineTo(startX + 3, startY);
      path.rLineTo(0, dz);
      path.rLineTo(-6, 0);
      path.close();
      Matrix mat = new Matrix();
      mat.reset();
      mat.setSinCos(-dx / dz, dy / dz, startX, startY);
      path.transform(mat);
    }

    private void lineEnd(int point) {
      PointF p = coordinate.get(point);
      end = point;
      if ((end | start) <= 3 || (end & start) >= 4) {
        empty();
      } else {
        lineTo(p.x, p.y);
      }
    }

    private void empty() {
      start = end = -1;
      path.reset();
    }

    private boolean isEmpty() {
      return start == end;
    }

    private boolean isSameStartOrEnd(Line line) {
      return contains(line.start) || contains(line.end);
    }

    private boolean contains(int point) {
      return start == point || end == point;
    }

    /**
     * @return 从0开始
     */
    public int getStart() {
      return start;
    }

    public int getEnd() {
      return end;
    }

  }

  private class PerformConnect implements Runnable {
    private Line line;

    public void setLine(Line connect) {
      line = connect;
    }

    @Override
    public void run() {
      if (null != line && !line.isEmpty()) {
        performLineConnected(line);
      }
    }

  }

  public interface OnLineConnectedListener {
    public void onLineConnected(Line line);
  }

}
